package ru.elifantiev.cityrouter.infrastructure.map.Yandex;

import android.content.Context;
import android.content.Intent;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.FrameLayout;
import com.google.android.maps.GeoPoint;
import ru.elifantiev.cityrouter.R;
import ru.elifantiev.cityrouter.infrastructure.map.MapWrapper;
import ru.elifantiev.cityrouter.infrastructure.map.AbstractMapWrapper;
import ru.elifantiev.cityrouter.infrastructure.map.ExternalZoomable;
import ru.elifantiev.cityrouter.infrastructure.map.LocationUtility;
import ru.elifantiev.cityrouter.infrastructure.map.ViewCache;
import ru.elifantiev.cityrouter.util.AssetProvider;
import ru.elifantiev.cityrouter.util.DrawableSerializer;

import java.io.IOException;

public class YandexMapWrapper extends AbstractMapWrapper<WebView> implements ExternalZoomable, LocationListener {

    private static final String MY_LOCATION_STYLE = "MyLocationStyle";
    private static final String FOUND_LOCATION_STYLE = "FoundLocationStyle";
    private static final String POINT_STYLE = "PointStyle";
    private static final String FOUND_LOCATION_OVERLAY = "FoundLocationOverlay";

    private boolean firstFix = true;
    private boolean showingMyLocation = false;
    private OnMapReadyListener readyListener = null;
    private OnClickListener clickListener = null;
    private LocationManager locationManager;
    private Handler clientInterfaceHandler;
    private String TAG = "YandexMapWrapper";
    private Location currentLocation = null;

    public YandexMapWrapper(ViewCache viewCache, final Context ctx, FrameLayout container, int mapResourceId) {

        super(viewCache, container, mapResourceId);

        clientInterfaceHandler = new Handler();
        mapContainerView.getSettings().setJavaScriptEnabled(true);
        mapContainerView.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                if (url.startsWith("http://")) {
                    ctx.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
                    return true;
                } else
                    return false;
            }

            @Override
            public void onPageFinished(WebView view, String url) {
                AssetManager assets = ctx.getAssets();
                try {
                    String[] jsFiles = assets.list("js");
                    for (String file : jsFiles)
                        execJSCommand(AssetProvider.getStringAsset(assets, "js/" + file));
                } catch (IOException e) {
                    Log.e(TAG, "Error while loading JS-assets", e);
                }
                execJSCommand("done();");
            }
        });
        mapContainerView.addJavascriptInterface(new ClientSideInterface() {

            public void notifyOnReady() {
                registerOverlayIcon(
                        MY_LOCATION_STYLE,
                        ctx.getResources(),
                        R.drawable.ic_maps_indicator_current_position,
                        -0.5f,
                        -0.5f);

                registerOverlayIcon(
                        FOUND_LOCATION_STYLE,
                        ctx.getResources(),
                        R.drawable.ic_flag,
                        -0.5f,
                        -1f);

                clientInterfaceHandler.post(new Runnable() {
                    public void run() {
                        if (readyListener != null)
                            readyListener.mapReady(YandexMapWrapper.this);
                    }
                });
            }

            public void notifyMapClick(final double latitude, final double longitude) {
                clientInterfaceHandler.post(new Runnable() {
                    public void run() {
                        if (clickListener != null) {
                            clickListener.onClick(
                                    YandexMapWrapper.this,
                                    new GeoPoint((int) (latitude * 1E6), (int) (longitude * 1E6)));
                        }
                    }
                });
            }
        }, "Client");

        locationManager = (LocationManager) ctx.getSystemService(Context.LOCATION_SERVICE);
        String pageContent;
        try {
            pageContent = AssetProvider.getStringAsset(ctx.getAssets(), "index.html");
        } catch (IOException e) {
            Log.e(TAG, "Error while loading JS-assets", e);
            pageContent = String.format(ctx.getString(R.string.errorHTML), e.getMessage());
        }

        mapContainerView.loadDataWithBaseURL(
                "http://transport.elifantiev.ru",
                pageContent,
                "text/html",
                "windows-1251",
                "http://transport.elifantiev.ru");
    }

    public void setCenter(GeoPoint center) {
        execJSCommand(
                String.format(
                        "Map.setCenter(%s, %s);",
                        String.valueOf(center.getLatitudeE6()),
                        String.valueOf(center.getLongitudeE6())));
    }

    public void setMode(boolean mode) {
        execJSCommand(String.format("Map.setMode(%s);", mode ? "true" : "false"));
    }

    public void setZoom(int zoom) {
        execJSCommand(String.format("Map.setZoom(%d);", zoom));
    }

    public void showMyLocation(boolean isShow) {
        if (isShow) {
            requestLocationUpdates();
            gotoMyLocation();
        } else {
            cancelLocationUpdates();
            firstFix = true;
        }
        showingMyLocation = isShow;
    }

    public void gotoMyLocation() {
        Location best = LocationUtility.getLastLocation(locationManager);
        if (best != null)
            setCenter(LocationUtility.pointFromLocation(best));
    }

    public void clearPoints() {
        execJSCommand("Map.clearPoints();");
    }

    private void cancelLocationUpdates() {
        locationManager.removeUpdates(this);
        execJSCommand("Map.hideMyLocation();");
    }

    private void requestLocationUpdates() {
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
        locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this);
    }

    public void setOnMapReadyListener(OnMapReadyListener listener) {
        this.readyListener = listener;
    }

    public void showPoint(int id, GeoPoint center, int radius) {
        execJSCommand(
                String.format("Map.showPointOverlay('point-%d', '%s', %d, %d, %d);",
                        id,
                        POINT_STYLE,
                        center.getLatitudeE6(),
                        center.getLongitudeE6(),
                        radius));
    }

    public void showMyLocation(GeoPoint location, int accuracy) {
        execJSCommand(
                String.format("Map.showMyLocation('%s', %d, %d, %d);",
                        MY_LOCATION_STYLE,
                        location.getLatitudeE6(),
                        location.getLongitudeE6(),
                        accuracy));
    }

    public void pause() {
        if (showingMyLocation)
            cancelLocationUpdates();
    }

    public void resume() {
        if (showingMyLocation)
            requestLocationUpdates();
    }

    public void destroy() {
        pause();
        mapContainerView.stopLoading();
        clickListener = null;
        readyListener = null;
    }

    public void showFoundLocationOverlay(GeoPoint location) {
        setCenter(location);
        showOverlayAt(FOUND_LOCATION_OVERLAY, FOUND_LOCATION_STYLE, location);
    }

    public void zoomOut() {
        execJSCommand("Map.zoomBy(-1);");
    }

    public void zoomIn() {
        execJSCommand("Map.zoomBy(1);");
    }

    public void setOnClickHandler(OnClickListener listener) {
        clickListener = listener;
    }

    public void onLocationChanged(Location location) {
        if (LocationUtility.isBetterLocation(location, currentLocation, 15 * 1000)) {
            GeoPoint point = LocationUtility.pointFromLocation(currentLocation = location);
            if (firstFix) {
                setCenter(point);
                firstFix = false;
            }
            showMyLocation(point, (int) location.getAccuracy());
        }
    }

    public void onStatusChanged(String s, int i, Bundle bundle) {
    }

    public void onProviderEnabled(String s) {
    }

    public void onProviderDisabled(String s) {
    }

    private void showOverlayAt(String overlayName, String overlayStyle, GeoPoint where) {
        execJSCommand(
                String.format("Map.showOverlayAt('%s', '%s', %d, %d);",
                        overlayName,
                        overlayStyle,
                        where.getLatitudeE6(),
                        where.getLongitudeE6()));
    }

    private void hideOverlay(String overlayName) {
        execJSCommand(String.format("Map.hideOverlay('%s');", overlayName));
    }

    private void registerOverlayIcon(String styleName, Resources res, int resourceId, float offsetX, float offsetY) {
        Drawable icon = res.getDrawable(resourceId);
        float width = icon.getIntrinsicWidth();
        float height = icon.getIntrinsicWidth();
        execJSCommand(
                String.format("Map.registerOverlay('%s', '%s', %d, %d, %d, %d);",
                        styleName,
                        DrawableSerializer.serializeToDataURL(res, resourceId),
                        (int) width,
                        (int) height,
                        (int) (width * offsetX),
                        (int) (height * offsetY)));
    }

    private void execJSCommand(String command) {
        mapContainerView.loadUrl("javascript:" + command);
    }
}
